package org.digitarts.rtms.spring.collectors.shell.commands;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.springframework.beans.factory.BeanFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.apache.log4j.Logger;
import org.digitarts.rtms.helpers.XMLHelper;
import org.digitarts.rtms.spring.collectors.formatter.DeferredFormatter;
import org.digitarts.rtms.spring.collectors.formatter.IFormatter;
import org.digitarts.rtms.tracing.Trace;

public class Command
{
	protected String						shellCommand				= null;
	protected String						paragraphSplitter			= "\\\r\\\n\\\r\n";
	protected List<CommandResultExtract>	extracts					= new ArrayList<CommandResultExtract>();
	protected Pattern						paragraphSplitterPattern	= null;
	protected Map<Integer, Integer>			headers						= new HashMap<Integer, Integer>();
	protected Map<Integer, IFormatter>		preFormatters				= new HashMap<Integer, IFormatter>();
	protected BeanFactory					beanFactory					= null;
	protected boolean						commandBeanFactoriesSet		= false;
	protected static String					eol							= System.getProperty("line.separator", "\n");
	protected Logger						log							= null;

	public Command()
	{
		log = Logger.getLogger(getClass());
	}

	public Command(String xml)
	{
		this(XMLHelper.parseXML(xml).getDocumentElement());
	}

	public Command(Element xml)
	{
		this();
		Node shellCommandNode = XMLHelper.getChildNodeByName(xml, "shellCommand", false);
		if (shellCommandNode == null)
			throw new RuntimeException("shellCommand is mandatory");
		shellCommand = XMLHelper.getNodeTextValue(shellCommandNode);
		if (shellCommand == null || shellCommand.length() < 1)
			throw new RuntimeException("shellCommand is null or blank");
		Node pgraphSplitterNode = XMLHelper.getChildNodeByName(xml, "paragraphSplitter", false);
		if (pgraphSplitterNode != null)
		{
			paragraphSplitter = XMLHelper.getNodeTextValue(pgraphSplitterNode);
		}
		List<Node> headerNodes = XMLHelper.getChildNodesByName(xml, "header", false);
		for (Node headerNode : headerNodes)
		{
			headers.put(XMLHelper.getAttributeIntValueByName(headerNode, "to"), XMLHelper.getAttributeIntValueByName(headerNode, "from"));
		}
		List<Node> preFormatterNodes = XMLHelper.getChildNodesByName(xml, "preFormatter", false);
		int i = 0;
		IFormatter formatter = null;
		for (Node pfNode : preFormatterNodes)
		{
			String formatterBeanName = XMLHelper.getAttributeValueByName(pfNode, "beanName");
			String className = XMLHelper.getAttributeValueByName(pfNode, "className");
			if (formatterBeanName == null && className == null)
				throw new RuntimeException("PreFormatter had neither a class name or a bean name.");
			if (formatterBeanName != null && className != null)
				throw new RuntimeException("PreFormatter had both a class name or a bean name.");
			if (className != null)
			{
				try
				{
					formatter = (IFormatter) Class.forName(className).newInstance();
				}
				catch (Exception e)
				{
					throw new RuntimeException("Failed to instantiated PreFormatter instance from class name [" + className + "]", e);
				}
			}
			else
			{
				formatter = new DeferredFormatter(formatterBeanName);
			}
			preFormatters.put(i, formatter);
		}
		paragraphSplitterPattern = Pattern.compile(paragraphSplitter);
		Node extractsNode = XMLHelper.getChildNodeByName(xml, "Extractors", false);
		if (extractsNode != null)
		{
			List<Node> extractNodes = XMLHelper.getChildNodesByName(extractsNode, "CommandResultExtract", false);
			for (Node node : extractNodes)
			{
				extracts.add(new CommandResultExtract((Element) node));
			}
		}
	}

	public Set<Trace> processResult(String result, Map<String, Object>... context)
	{
		Set<Trace> traces = new HashSet<Trace>();
		IFormatter formatter = null;
		if (!commandBeanFactoriesSet)
		{
			for (int i = 0; i < preFormatters.size(); i++)
			{
				formatter = preFormatters.get(i);
				if (formatter instanceof DeferredFormatter)
				{
					((DeferredFormatter) formatter).setBeanFactory(beanFactory);
				}
			}
			commandBeanFactoriesSet = true;
		}
		for (int i = 0; i < preFormatters.size(); i++)
		{
			formatter = preFormatters.get(i);
			result = formatter.format(result);
		}
		String[] paragraphs = paragraphSplitterPattern.split(result);
		for (Entry<Integer, Integer> pMap : headers.entrySet())
		{
			// Map is keyed by "to"
			int from = pMap.getValue();
			int to = pMap.getKey();
			StringBuilder headerParagraph = new StringBuilder(paragraphs[from]);
			headerParagraph.append(paragraphs[to]).append(eol);
			paragraphs[to] = headerParagraph.toString();
		}

		for (CommandResultExtract extract : extracts)
		{
			try
			{
				traces.addAll(extract.processResultParagraph(paragraphs[extract.getParagraphId()]));
			}
			catch (Exception e)
			{
				log.warn("CommandResultExtract Failed for(" + this.getShellCommand() + "/" + extract.getParagraphName() + ")", e);
			}

		}
		//
		return traces;
	}

	public String getShellCommand()
	{
		return shellCommand;
	}

	public void setShellCommand(String shellCommand)
	{
		this.shellCommand = shellCommand;
	}

	public String getParagraphSplitter()
	{
		return paragraphSplitter;
	}

	public void setParagraphSplitter(String paragraphSplitter)
	{
		this.paragraphSplitter = paragraphSplitter;
	}

	public String toString()
	{
		final String TAB = "    \n\t";

		StringBuilder retValue = new StringBuilder();

		retValue.append("Command ( ").append("shellCommand = ").append(this.shellCommand).append(TAB).append("paragraphSplitter = ").append(this.paragraphSplitter).append(TAB).append("extracts=")
				.append(extracts.size()).append("\n").append(" )");

		return retValue.toString();
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((shellCommand == null) ? 0 : shellCommand.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final Command other = (Command) obj;
		if (shellCommand == null)
		{
			if (other.shellCommand != null)
				return false;
		}
		else if (!shellCommand.equals(other.shellCommand))
			return false;
		return true;
	}

	public Map<Integer, IFormatter> getPreFormatters()
	{
		return preFormatters;
	}

	public void setPreFormatters(Map<Integer, IFormatter> preFormatters)
	{
		this.preFormatters = preFormatters;
	}

	public BeanFactory getBeanFactory()
	{
		return beanFactory;
	}

	public void setBeanFactory(BeanFactory beanFactory)
	{
		this.beanFactory = beanFactory;
	}

}
